#!/usr/bin/env python
import sys
import os
import re
from datetime import datetime
import readline
from time import time
from cmd import Cmd
from pluke import Pluke
from lucene import Hit
from lucene import initVM, CLASSPATH
from lucene import VERSION

"""
   Pluke: PyLucene Command Line tool
   $Id$
"""

class PlukeRunner(Cmd):

    intro = """Pluke %s for PyLucene %s.

  Type: \open   [INDEX_PATH]  to open a lucene index
        \create [DIRECTORY]   to create a lucene index based for the directory
        \?                    for help
        \q or ^D              to quit.""" % (Pluke.VERSION, VERSION)

    pluke_attr = ('limit', 'returnFields', 'queryField', 
                  'threshold', 'analyzer')
    queryField = 'index'
    returnFields = 'content'
    limit = 3
    analyzer = 'Simple'
    plukeUsrPath = '~/.pluke'
    historyPath = '~/.pluke/history'
    threshold = 0
    timeout = 0
    _pluke = None

    # for Pluke
    def __init__(self, indexDir):
        Cmd.__init__(self)
        self._pluke = Pluke(indexDir)
        self.prompt = '> '
        if self._pluke.connected():
            self.prompt = 'pluke> '
        self._doQuery = True
        self.historyPath = os.path.expanduser(self.historyPath)
        self.plukeUsrPath = os.path.expanduser(self.plukeUsrPath)
        self.queryHistory = []
        if not os.path.exists(self.plukeUsrPath):
            os.mkdir(self.plukeUsrPath)
        if os.path.exists(self.historyPath):
            readline.read_history_file(self.historyPath)

    def _IndexDirConnected(self):
        if not self._pluke.connected():
            print "Error: You must open a index first.",
            print "type '\?' for more information."
        return self._pluke.connected()

    def _getResultFields(self):
        """Return list of selected fields."""
        return filter(None, self.returnFields.split())

    def analyzers(self):
        return self._pluke.getAvailableAnalyzerNames

    def query(self, terms):
        print "Query Terms: %s" % terms
        print "Anayzer: %s" % self.analyzer
        print "Search index: %s" % self.queryField
        print "Threshold: %.2fs" % self.threshold
        print "------------------------------------"
        t_start = time()
        try:
            hits = self._pluke.search(terms, fieldName=self.queryField,
                            analyzerName=self.analyzer)
        except Exception, e:
            print "query: ", e
            return 0
        if hits is None:
            return 0
        delta = time() - t_start
        numHits = hits.length()
        print "  Found %s matching document(s) in %.3fs" % (numHits, delta)
        if numHits:
            print
        i = 0
        limit = self.limit
        threshold = self.threshold
        count = 0
        resultFields = self._getResultFields()
        for hit in hits:
            hit = Hit.cast_(hit)
            doc = hit.getDocument() 
            doc_num = hit.getId()
            if threshold and hit.getScore() < threshold:
                continue
            if limit and count > limit:
                print "----- LIMIT %d ----" % self.limit
                break
            print "* %2.2d) score: %.2f, doc_num: %s" % (count, hit.getScore(),
                                                         doc_num)
            for field in resultFields:
                print '  %s: %s' % (field, doc.get(field))
            print
            count += 1
        if count:
            print "  Found %s matching document(s) in %.3fs" % (numHits, delta)
            self.queryHistory.append((self.analyzer, terms, numHits, delta))
        if threshold:
            print "%d document with score >= %.2fs" % (count, threshold)
        return 0

    # for Cmd
    def do_help(self, messge):
        """\open [INDEX_PATH]    to open a lucene index
           \info                 information about the data source
        """
        return 0

    def do_quit(self, message):
        """\q[uit]
        Quit."""
        readline.write_history_file(self.historyPath)
        return 1
    do_exit = do_quit
    do_q = do_quit

    def do_info(self, message):
        """\info or \I
        Information about the data source."""
        if not self._IndexDirConnected():
            return
        numDocs = self._pluke.numDocs()
        maxDoc = self._pluke.maxDoc()
        fields = self._pluke.getFieldInfos()
        print "Directory info"
        print "--------------"
        print "* Directory path             : %s" % self._pluke.getDirectory()
        print "* Directory current version  : %s" % self._pluke.getVersion()
        print "* Number of docs             : %s (max doc num: %s)" % (
            numDocs, maxDoc)
        print "* Number of fields           : %d" % len(fields)
        print "* Index status               :",
        if self._pluke.isLocked():
            print "Locked"
        else:
            print "Unlocked"
        print "* Has deletions              :",
        if self._pluke.hasDeletions():
            print "Yes"
        else:
            print "No"
    do_I = do_info

    def do_indexes(self, message):
        """\indexes [DOC_NUM] or \di [DOC_NUM]
        Indexes for DOC_NUM."""
        if not self._IndexDirConnected():
            return
        print "Index info"
        print "----------"
        maxDoc = self._pluke.maxDoc()
        numDoc = maxDoc - 1
        if message:
            try:
                numDoc = int(message)
            except ValueError:
                print "do_indexes: Invalid value for doc num"
            if numDoc < 0 or numDoc >= maxDoc:
                print "do_indexes: Invalid doc num"
                numDoc = maxDoc - 1
        fields = self._pluke.getFieldInfos(numDoc)
        print "Found %d fields for doc #%d:" % (len(fields), numDoc)
        for field in fields:
            print "* %-27s  %s %s %s %-10s" % (
                field[0],
                field[1] and 'Indexed' or '       ',
                field[2] and 'Tokenized' or '        ',
                field[3] and 'Binary' or '      ',
                field[4] and 'Compressed' or '         ')
    do_i = do_indexes

    def do_view(self, message):
        """\\v[iew] [DOC_NUM [FIELD_PATTERN]]
        View values of index fields that matches PATTERN for DOC_NUM

        ex:
        \\v 123
        \\v 123 name|path"""
        if not self._IndexDirConnected():
            return
        maxDoc = self._pluke.maxDoc()
        numDoc = maxDoc - 1
        pattern = None
        if message:
            words = message.split(' ', 1)
            numDoc = None
            pattern = None
            if len(words) >= 1:
                numDoc = words[0].strip()
            if len(words) == 2:
                pattern = words[1].strip()
            try:
                numDoc = int(numDoc)
            except ValueError:
                print "do_view: Invalid value for doc num"
        if pattern:
            try:
                pat = re.compile(pattern)
            except RegexError:
                print "do_view: '%s' is an invalid regex" % pattern
                pattern = None

        fields = self._pluke.getFieldInfos(numDoc)
        print "Found %d fields for doc #%d" % (len(fields), numDoc)
        for field in fields:
            if pattern is not None and not pat.search(field[0]):
                continue
            print "%-25s  %s %s %s %s: %s" % (field[0],
                                                 field[1] and 'I' or ' ',
                                                 field[2] and 'T' or ' ',
                                                 field[3] and 'B' or ' ',
                                                 field[4] and 'C' or ' ',
                                                 field[5] )
    do_v = do_view

    def do_open(self, dir):
        """\open [INDEX_PATH]
        Open a lucene index.

        ex:
        \open /path/to/the/lucene/index"""
        if not dir:
            return
        if self._pluke.openIndexDir(dir) is True:
            self.prompt = 'pluke> '

    def do_close(self):
        """\close
        Close the current index."""
        self.finishIndexDir()
        self.prompt = '> '

    def do_create(self, dir):
        """\create [DIRECTORY] [sfx=".txt"]
        Create a index for the [DIRECTORY]."""
        self._pluke.createIndexDir(dir)
        self.prompt = '> '

    def do_set(self, message):
        """\set [NAME [VALUE]]
        Set config variable, or list all if no parameters.

        Where NAME can be:
        analyzer       select one of the lucene analyzer : Whitespace, Keyword,
                       Stop, Standard, Simple.
        limit          limit the result number.
        queryField     index field used for query.
        select         fields to display.
        threshold      filter out score < threshold.
        timeout        stop while search time exceed timeout (in Sec.)

        ex:
        \set analyzer Simple
        \set limit 10
        \set queryField content
        \set select id text
        \set threshold 5
        \set timeout 5
        """
        words = message.split(' ', 1)
        key = None
        value = None
        if len(words) >= 1:
            key = words[0].strip()
        if len(words) == 2:
            value = words[1].strip()
        if not key:
            for key in self.pluke_attr:
                print "  %s = %s" % (key, getattr(self, key))
            return
        if key not in self.pluke_attr:
            print "KeyError   : '%s' is not a valid key." % key
            print "Valid keys : %s." % ', '.join(self.pluke_attr)
            return
        if not value:
            print "  %s = %s" % (key, getattr(self, key, 'unset ?'))
            return
        if key == 'limit':
            try:
                value = int(value)
            except ValueError:
                print "do_set: Integer expected."
                return
        if key in ('threshold', 'timeout'):
            try:
                value = float(value)
            except ValueError:
                print "do_set: Float value expected."
                return
        if key == 'analyzer':
            if value not in self.analyzers():
                print "AnalyzerError      : Invalid analyzer '%s'." % value
                print "Availabe analyzers : %s" \
                    % ( '/'.join(self.analyzers()),)
                return
        setattr(self, key, value)
        print "  %s setted." % key
        return 0
    do_s = do_set

    def default(self, line):
        """A lucene query."""
        if not self._IndexDirConnected():
            return 0
        if not self._doQuery:
            print "Invalid command."
            return 0
        return self.query(line)

    def cmdline(self, line):
        """do cmd line.

        used by doc test."""
        line = self.precmd(line)
        stop = self.onecmd(line)
        stop = self.postcmd(stop, line)

    doc_header = "Help commands (use '\help <topic>'):"
    def precmd(self, line):
        """Escaping backslash cmd"""
        if line.startswith('\\'):
            self._doQuery = False
            return line[1:]
        self._doQuery = True
        return line

    def parseline(self, line):
        """Only parseline when not doing a query"""
        if self._doQuery is False:
            return Cmd.parseline(self, line)
        if line == "EOF":
            return ('quit', None, line)
        return (None, None, line)

    def complete_open(self, text, line, begidx, endidx):
        """Completion for the open cmd."""
        if not text:
            return os.listdir('./')
        if not os.access(text, os.F_OK):
            return [x for x in os.listdir('./') if x.startswith(text)]
        else:
            l += [text]
            files = os.listdir(text)
            for file in files:
                if textlen > 0 and file[:textlen] != text:
                    continue
                if os.access(text+'/'+file, os.F_OK):
                    l += [text+'/'+file]
        return l

    def complete_set(self, text, line, begidx, endidx):
        """Completion for the set cmd."""
        if 'analyzer' in line:
            completions = self.analyzers()
        else:
            completions = self.pluke_attr
        return [attr for attr in completions if attr.startswith(text)]

    def complete(self, text, state):
        """Overwrites the complete function."""
        if state == 0:
            origline = readline.get_line_buffer()
            line = origline.lstrip()
            if not line.startswith('\\'):
                return None
            line = line[1:]
            stripped = len(origline) - len(line)
            begidx = readline.get_begidx() - stripped
            endidx = readline.get_endidx() - stripped
            if begidx > 0:
                cmd, args, foo = self.parseline(line)
                if cmd == '':
                    compfunc = self.completedefault
                else:
                    try:
                        compfunc = getattr(self, 'complete_' + cmd)
                    except AttributeError:
                        compfunc = self.completedefault
            else:
                compfunc = self.completenames
            self.completion_matches = compfunc(text, line, begidx, endidx)
        try:
            return self.completion_matches[state]
        except IndexError:
            return None

if __name__ == '__main__':
    start = datetime.now()                                                                                                        
    INDEXPATH = ""
    if len(sys.argv) > 1:
        INDEXPATH = sys.argv[1]
    initVM(CLASSPATH)
    try:
        pluke = PlukeRunner(INDEXPATH)
        pluke.cmdloop()
        end = datetime.now()
        print end - start
    except Exception, e: 
        print "Main Exception: ", e
